package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregator;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @author atguigu-mqx
 */
@Service
public class SearchServiceImpl implements SearchService {

    //  将数据库中的数据保存到es ！ 但是需要借助 Goods 实体类！
    //  需要引入操作es 的客户端，或者是工具类！
    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;


    @Override
    public void upperGoods(Long skuId) {
        //  创建一个Goods
        Goods goods = new Goods();
        //  给goods 赋值！
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setId(skuInfo.getId());
            goods.setTitle(skuInfo.getSkuName());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setPrice(skuInfo.getPrice().doubleValue());
            goods.setCreateTime(new Date());
            return skuInfo;
        }, threadPoolExecutor);

        //  获取品牌数据
        CompletableFuture<Void> trademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());

        }, threadPoolExecutor);

        //  获取分类数据
        CompletableFuture<Void> completableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());

            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());
        }, threadPoolExecutor);
        //  获取平台属性
        CompletableFuture<Void> attrListCompletableFuture = CompletableFuture.runAsync(() -> {
            List<BaseAttrInfo> attrList = this.productFeignClient.getAttrList(skuId);

            //  一个skuId 对应平台属性值 只有一个！
            //  private List<SearchAttr> attrs;
            List<SearchAttr> attrs = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //    平台属性值名
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;

            }).collect(Collectors.toList());
            goods.setAttrs(attrs);

        }, threadPoolExecutor);

        //  多任务组合
        CompletableFuture.allOf(skuInfoCompletableFuture,
                trademarkCompletableFuture,
                completableFuture,
                attrListCompletableFuture).join();

        this.goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        //  删除数据
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //  获取redis 增长的值！
        String hotKey = "hotScore";
        //  zincrby salary 1000 tom
        //  第一个：key 第二个：tom 第三个：1000
        Double count = this.redisTemplate.opsForZSet().incrementScore(hotKey, "sku:" + skuId, 1);
        if (count % 10 == 0) {
            //  将值写入es中！
            Optional<Goods> option = this.goodsRepository.findById(skuId);
            Goods goods = option.get();
            goods.setHotScore(count.longValue());
            //  修改完成之后，赋值！
            this.goodsRepository.save(goods);
        }

    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {

        /*
            1.  生成dsl
            2.  执行dsl语句
            3.  获取结果数据并转换为SearchResponseVo 对象！
         */
        //  查询请求对象
        //        SearchRequest searchRequest = new SearchRequest();
        //        //  查询器 {}
        //        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //        //  {query }
        //        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        //        searchRequest.source(searchSourceBuilder);
        //        try {
        //            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //        } catch (IOException e) {
        //            e.printStackTrace();
        //        }
        //  声明一个查询对象，在这个对象中有生成的DSL 语句。
        SearchRequest searchRequest = this.searchDslBuild(searchParam);
        //  调用高级客户端查询数据！
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //  声明一个对象 SearchResponseVo
        SearchResponseVo searchResponseVo = this.parseResultSearchResponse(searchResponse);
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数  searchResponseVo.setTotal(searchResponse.getHits().getTotalHits().value);
        以上四个属性 在 parseResultSearchResponse()方法中进行赋值！
        ----------------------------------------------------------
        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;
         */
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  默认显示3条数据
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //  totalPages 总页数 10 3 4 | 9 3 3
        //  Long totalPages = searchResponseVo.getTotal()%searchParam.getPageSize()==0?searchResponseVo.getTotal()/searchParam.getPageSize():searchResponseVo.getTotal()/searchParam.getPageSize()+1
        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageSize() - 1) / searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);

        //  返回数据！
        return searchResponseVo;
    }

    private SearchResponseVo parseResultSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数  searchResponseVo.setTotal(searchResponse.getHits().getTotalHits().value);
         */
        SearchHits hits = searchResponse.getHits();
        //  获取到总记录数！
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //  商品集合数据来自于：
        SearchHit[] subHits = hits.getHits();
        //  循环遍历
        List<Goods> goodsList = new ArrayList<>();
        for (SearchHit subHit : subHits) {
            String sourceAsString = subHit.getSourceAsString();
            //  sourceAsString JSON 变为Goods
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //  不够优秀： 分类Id 不需要高亮， 关键词 需要高亮！
            if(subHit.getHighlightFields().get("title")!=null){
                //  需要高亮：
                Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                goods.setTitle(title.toString());
            }
            //  将商品添加到集合
            goodsList.add(goods);
        }
        //  赋值商品集合数据
        searchResponseVo.setGoodsList(goodsList);

        //  从聚合中获取品牌数据：
        //  Aggregation 变为了map 集合
        Map<String, Aggregation> stringAggregationMap = searchResponse.getAggregations().asMap();
        //  tmId ---> Long 数据类型存储！
        //  Aggregation ---> ParsedLongTerms 目的获取到桶！
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) stringAggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            //  声明对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            //  赋值品牌Id
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));
            //  赋值品牌tmName , 需要先获取tmNameAgg   //  Aggregation ---> ParsedStringTerms 目的获取到桶！
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //  赋值品牌tmLogoUrl
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        //  给品牌赋值：
        searchResponseVo.setTrademarkList(trademarkList);

        //  从聚合中获取数据：
        //  平台属性值 聚合 nested 需要获取到 attrs
        ParsedNested attrAgg = (ParsedNested) stringAggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");

        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //  Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            //  赋值平台属性Id
            searchResponseAttrVo.setAttrId(Long.parseLong(keyAsString));
            //  赋值平台属性名
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //  赋值平台属性值名称
//            List<String> valueNameList = new ArrayList<>();
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
//            for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
//                String valueName = attrValueAggBucket.getKeyAsString();
//                valueNameList.add(valueName);
//            }
            //  使用拉姆达表达式：
            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());

            //  赋值平台属性值名称
            searchResponseAttrVo.setAttrValueList(valueNameList);

            return searchResponseAttrVo;
        }).collect(Collectors.toList());

        //  给平台属性赋值：
        searchResponseVo.setAttrsList(attrsList);

        return searchResponseVo;
    }

    /**
     * 动态生成dsl 语句！
     *
     * @param searchParam
     * @return
     */
    private SearchRequest searchDslBuild(SearchParam searchParam) {
        //  定义一个查询器：
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  {query --- bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //  判断用户是否根据关键词查询！ 第一个入口！
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            //  {query --- bool--- must --- match }
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
        }

        //  判断用户是否根据分类Id 进行过滤！
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())) {
            //  {query --- bool --- filter --- term }
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }

        if (!StringUtils.isEmpty(searchParam.getCategory2Id())) {
            //  {query --- bool --- filter --- term }
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }

        if (!StringUtils.isEmpty(searchParam.getCategory1Id())) {
            //  {query --- bool --- filter --- term }
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        //  根据品牌Id 生成dsl 语句！ trademark=1:小米
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] split = trademark.split(":");
            if (split != null && split.length == 2) {
                //  {query --- bool --- filter --- term }
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
            }
        }
        //  平台属性值过滤！
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            //  props=24:256G:机身内存&props=107:小米:二级手机
            for (String prop : props) {
                String[] split = prop.split(":");
                if (split != null && split.length == 3) {
                    //  构建DSL语句结构：
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    //  最内存的bool
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                    //  赋值nested 类型！
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));

                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }

        //{ query --- bool }
        searchSourceBuilder.query(boolQueryBuilder);
        //  排序： 综合： order=1:desc order=1:asc  价格： order=2:desc order=2:asc
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //  分割：
            String[] split = order.split(":");
            //  JavaScript === == =
            if (split != null && split.length == 2) {
                //  声明一个按照什么类型排序字段
                String field = null;
                //  JDK1.7 以后可以判断String
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                //  设置排序
                searchSourceBuilder.sort(field, "asc".equals(split[1]) ? SortOrder.ASC : SortOrder.DESC);
            } else {
                searchSourceBuilder.sort("hotScore", SortOrder.DESC);
            }
        }

        //  分页：
        //  表示从第几条开始查询数据！ 记住公式！
        //  10 3 4  9 3 3
        //  第一页： 0,3 第二页 3,3     mysql limit  www.baidu.com
        int from = (searchParam.getPageNo() - 1) * searchParam.getPageSize();
        searchSourceBuilder.from(from);
        //  每页显示的条数
        searchSourceBuilder.size(searchParam.getPageSize());

        //  高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //  聚合： 品牌
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //  聚合: 平台属性
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                )
        );
        //  打印出dsl 语句！
        System.out.println("dsl:\t"+searchSourceBuilder.toString());
        //  声明一个对象  GET /goods/_search {dsl }
        SearchRequest searchRequest = new SearchRequest("goods");
        //  type 可以省略.....
        //  设置查询时需要显示的字段：
        searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price","createTime"},null);
        //  赋值！
        searchRequest.source(searchSourceBuilder);
        //  返回数据！
        return searchRequest;
    }
}
